﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Brio.Framework
{
    [Serializable]
    public class MotifMetricTechnique
    {
        public MotifMetricTechnique(Technique technique)
        {
            Name = technique.Name;
            OriginalPointValue = technique.Value;
            ExponentialDecay = technique.DecayRate;

            TotalPoints = 0.0;
            NumberOfOccurances = 0;
            AverageRatio = 0;
            AveragePointValue = 0.0;
        }

        public string Name { get; set; }
        public double TotalPoints { get; set; }
        public double ExponentialDecay { get; set; }
        public int NumberOfOccurances { get; set; }
        public double OriginalPointValue { get; set; }
        public double AverageRatio { get; set; }
        public double AveragePointValue { get; set; }
    }

    [Serializable]
    public class MotifMetricNeighbor
    {
        public MotifMetricNeighbor(Technique lhsTechnique, Technique rhsTechnique)
        {
            NeighborNameLHS = lhsTechnique.Name;
            NeighborNameRHS = rhsTechnique.Name;

            NeighborExponentialDecayLHS = lhsTechnique.DecayRate;
            NeighborExponentialDecayRHS = rhsTechnique.DecayRate;

            Multiplier = lhsTechnique.NeighborMultiplier;

            NumberOfOccurances = 0;
            TotalPoints = 0.0;
            AveragePointValue = 0.0;
        }

        public string NeighborNameLHS { get; set; }
        public string NeighborNameRHS { get; set; }
        public double NeighborExponentialDecayLHS { get; set; }
        public double NeighborExponentialDecayRHS { get; set; }
        public double Multiplier { get; set; }
        public int NumberOfOccurances { get; set; }
        public double TotalPoints { get; set; }
        public double AveragePointValue { get; set; }
    }


    [Serializable]
    public class MotifMetrics
    {
        public MotifMetrics()
        {
            TechniqueMetrics = new Dictionary<string, MotifMetricTechnique>();
            NeighborMetrics = new Dictionary<string, MotifMetricNeighbor>();
        }

        public Dictionary<string, MotifMetricTechnique> TechniqueMetrics
        { get; set; }

        public Dictionary<string, MotifMetricNeighbor> NeighborMetrics
        { get; set; }

        /// <summary>
        /// Make a pretty report for all to read.
        /// </summary>
        /// <returns>I'm so pretty, oh so pretty.</returns>
        public string ToMetricReportString()
        {
            StringBuilder builder = new StringBuilder();


            builder.AppendLine("=============");
            builder.AppendLine("Helpful Formulas");
            builder.AppendLine("=============");

            builder.AppendLine("Formula Cheat Sheet");

            builder.AppendLine();
            builder.AppendLine("Technique Equations");
            builder.AppendLine();
            builder.AppendLine("Total Points Technique_{i} = sum_{i to occurances}( weighting_{i} x tech_points_{i} x e^{-decay_rate_{i} x i )");
            builder.AppendLine("where weighting_{i} = min(1.0 , max(0.0, log_{10}( 10.0 / exploratoryratio x ratio_{i}) ) )");
            builder.AppendLine("where ratio_{i} = percentage of technique found in chromosome that wasn't marked from 0.0 to 1.0");
            builder.AppendLine("where i is the technique index");
            builder.AppendLine();
            builder.AppendLine("Plain English for Techniques");
            builder.AppendLine();
            builder.AppendLine(" Each technique has a point value which is the original point value or (tech_points_{i}). Each tech also has a decay");
            builder.AppendLine(" rate (decay_rate_{i}). The first time a tech is found it is given the most points. Subsequent times are exponentially");
            builder.AppendLine(" decayed based on the decay rate which discourages the technique from staying in the population. The ratio (ratio_{i})");
            builder.AppendLine(" means how much percentage of the original technique is intact. Depending on a parameter setting (exploratory ratio)  ");
            builder.AppendLine(" full points can be given to techniques that have a fraction of the full ratio but then exponentially decay if they are ");
            builder.AppendLine(" not within the min exploratory ratio. Plot weighting{i} from 0.0 to 1.0 and you can observe what it looks like.");

            builder.AppendLine();
            builder.AppendLine("Neighbor Equations");
            builder.AppendLine("");
            builder.AppendLine("Total_Point_Neighbor_{i} = (exp_decayed_point_value_lhs + exp_decayed_point_value_rhs) x neighbor_multiplier");
            builder.AppendLine("where exp_decayed_point_value_rhs = e^{-decay_rate_rhs_technique x number_of_rhs_technique_occurances}");
            builder.AppendLine("where exp_decayed_point_value_lhs = e^{-decay_rate_lhs_technique x number_of_lhs_technique_occurances}");
            builder.AppendLine("where neighbor_multiplier = [0 .. DOUBLE_MAX]");

            builder.AppendLine();
            builder.AppendLine("Plain English for Neighbors");
            builder.AppendLine("Neighbor relationships encourage structure in the staff. They give combination points of the LHS + RHS for");
            builder.AppendLine(" appearing next to eachother. To disencourage the same structure happening too frequently because of ");
            builder.AppendLine("high multiplier values the exponentially decayed value seen above of both the ");
            builder.AppendLine("left hand and right hand side techniques are added instead of the original.");


            builder.AppendLine("=============");
            builder.AppendLine("Techniques");
            builder.AppendLine("=============");
            
            // Technique Metrics
            for (int i = 0; i < TechniqueMetrics.Count; i++)
            {
                // Name of Technique
                builder.AppendLine(i + ") Technique " + TechniqueMetrics.ElementAt(i).Value.Name);

                builder.AppendLine("\t• Total Points = " + TechniqueMetrics.ElementAt(i).Value.TotalPoints);
                builder.AppendLine("\t• Exponential Decay (0 = none, >0 = decay) = " + TechniqueMetrics.ElementAt(i).Value.ExponentialDecay);
                builder.AppendLine("\t• Number of Times Found = " + TechniqueMetrics.ElementAt(i).Value.NumberOfOccurances);
                builder.AppendLine("\t• Original Point Value  = " + TechniqueMetrics.ElementAt(i).Value.OriginalPointValue);
                builder.AppendLine("\t• Average Ratio Of Original = " + TechniqueMetrics.ElementAt(i).Value.AverageRatio);
                builder.AppendLine("\t• Average Point Value = " + TechniqueMetrics.ElementAt(i).Value.AveragePointValue);
                builder.AppendLine();
            }
            

            builder.AppendLine("=============");
            builder.AppendLine("Neighbors");
            builder.AppendLine("=============");
            
            // Neighbor Metrics
            for (int i = 0; i < NeighborMetrics.Count; i++)
            {
                // Name of Technique
                builder.AppendLine(i + ") Neighbors " +
                NeighborMetrics.ElementAt(i).Value.NeighborNameLHS + " and " +
                NeighborMetrics.ElementAt(i).Value.NeighborNameRHS);

                builder.AppendLine("\t• Multiplier = " + NeighborMetrics.ElementAt(i).Value.Multiplier);
                builder.AppendLine("\t• LHS Exponential Decay (0 = none, >0 = decay) = " + NeighborMetrics.ElementAt(i).Value.NeighborExponentialDecayLHS);
                builder.AppendLine("\t• RHS Exponential Decay (0 = none, >0 = decay) = " + NeighborMetrics.ElementAt(i).Value.NeighborExponentialDecayRHS);
                builder.AppendLine("\t• Number of Times Found = " + NeighborMetrics.ElementAt(i).Value.NumberOfOccurances);
                builder.AppendLine("\t• Total Points = " + NeighborMetrics.ElementAt(i).Value.TotalPoints);
                builder.AppendLine("\t• Average Point Value = " + NeighborMetrics.ElementAt(i).Value.AveragePointValue);
                builder.AppendLine();
            }
            
            builder.AppendLine("=============");
            builder.AppendLine("Results");
            builder.AppendLine("=============");

            double techniquePoints = TechniqueMetrics.Sum(series => series.Value.TotalPoints);
            double neighborPoints = NeighborMetrics.Sum(series => series.Value.TotalPoints);
            double overallPoints = techniquePoints + neighborPoints;

            builder.AppendLine("Technique Points = " + techniquePoints);
            builder.AppendLine("Neighbor Points = " + neighborPoints);
            builder.AppendLine();
            builder.AppendLine("Total Points (Fitness) = " + overallPoints);

            return builder.ToString();
        }


       
    }
}
